/*
  ==============================================================================

   This file is part of the JUCE library.
   Copyright (c) 2013 - Raw Material Software Ltd.

   Permission is granted to use this software under the terms of either:
   a) the GPL v2 (or any later version)
   b) the Affero GPL v3

   Details of these licenses can be found at: www.gnu.org/licenses

   JUCE is distributed in the hope that it will be useful, but WITHOUT ANY
   WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
   A PARTICULAR PURPOSE.  See the GNU General Public License for more details.

   ------------------------------------------------------------------------------

   To release a closed-source product which uses JUCE, commercial licenses are
   available: visit www.juce.com for more information.

  ==============================================================================
*/


#if JUCE_COMPILER_SUPPORTS_LAMBDAS

//==============================================================================
struct AudioProcessorValueTreeState::Parameter   : public AudioProcessorParameter,
                                                   private ValueTree::Listener
{
    Parameter (AudioProcessorValueTreeState& s,
               String parameterID, String paramName, String labelText,
               NormalisableRange<float> r, float defaultVal,
               std::function<String (float)> valueToText,
               std::function<float (const String&)> textToValue)
        : owner (s), paramID (parameterID), name (paramName), label (labelText),
          valueToTextFunction (valueToText),
          textToValueFunction (textToValue),
          range (r), value (defaultVal), defaultValue (defaultVal),
          listenersNeedCalling (true)
    {
        state.addListener (this);
        needsUpdate.set (1);
    }

    ~Parameter()
    {
        // should have detached all callbacks before destroying the parameters!
        jassert (listeners.size() <= 1);
    }

    float getValue() const override                             { return range.convertTo0to1 (value); }
    float getDefaultValue() const override                      { return range.convertTo0to1 (defaultValue); }
    String getName (int maximumStringLength) const override     { return name.substring (0, maximumStringLength); }
    String getLabel() const override                            { return label; }

    float getValueForText (const String& text) const override
    {
        return range.convertTo0to1 (textToValueFunction != nullptr ? textToValueFunction (text)
                                                                   : text.getFloatValue());
    }

    String getText (float v, int length) const override
    {
        return valueToTextFunction != nullptr ? valueToTextFunction (range.convertFrom0to1 (v))
                                              : AudioProcessorParameter::getText (v, length);
    }

    void setValue (float newValue) override
    {
        newValue = range.snapToLegalValue (range.convertFrom0to1 (newValue));

        if (value != newValue || listenersNeedCalling)
        {
            value = newValue;

            listeners.call (&AudioProcessorValueTreeState::Listener::parameterChanged, paramID, value);
            listenersNeedCalling = false;

            needsUpdate.set (1);
        }
    }

    void setNewState (const ValueTree& v)
    {
        state = v;
        updateFromValueTree();
    }

    void setUnnormalisedValue (float newUnnormalisedValue)
    {
        if (value != newUnnormalisedValue)
        {
            const float newValue = range.convertTo0to1 (newUnnormalisedValue);
            setValueNotifyingHost (newValue);
        }
    }

    void updateFromValueTree()
    {
        setUnnormalisedValue (state.getProperty (owner.valuePropertyID, defaultValue));
    }

    void copyValueToValueTree()
    {
        if (state.isValid())
            state.setProperty (owner.valuePropertyID, value, owner.undoManager);
    }

    void valueTreePropertyChanged (ValueTree&, const Identifier& property) override
    {
        if (property == owner.valuePropertyID)
            updateFromValueTree();
    }

    void valueTreeChildAdded (ValueTree&, ValueTree&) override {}
    void valueTreeChildRemoved (ValueTree&, ValueTree&, int) override {}
    void valueTreeChildOrderChanged (ValueTree&, int, int) override {}
    void valueTreeParentChanged (ValueTree&) override {}

    static Parameter* getParameterForID (AudioProcessor& processor, StringRef paramID) noexcept
    {
        const int numParams = processor.getParameters().size();

        for (int i = 0; i < numParams; ++i)
        {
            AudioProcessorParameter* const ap = processor.getParameters().getUnchecked(i);

            // When using this class, you must allow it to manage all the parameters in your AudioProcessor, and
            // not add any parameter objects of other types!
            jassert (dynamic_cast<Parameter*> (ap) != nullptr);

            Parameter* const p = static_cast<Parameter*> (ap);

            if (paramID == p->paramID)
                return p;
        }

        return nullptr;
    }

    AudioProcessorValueTreeState& owner;
    ValueTree state;
    String paramID, name, label;
    ListenerList<AudioProcessorValueTreeState::Listener> listeners;
    std::function<String (float)> valueToTextFunction;
    std::function<float (const String&)> textToValueFunction;
    NormalisableRange<float> range;
    float value, defaultValue;
    Atomic<int> needsUpdate;
    bool listenersNeedCalling;

    JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (Parameter)
};

//==============================================================================
AudioProcessorValueTreeState::AudioProcessorValueTreeState (AudioProcessor& p, UndoManager* um)
    : processor (p),
      undoManager (um),
      valueType ("PARAM"),
      valuePropertyID ("value"),
      idPropertyID ("id"),
      updatingConnections (false)
{
    startTimerHz (10);
    state.addListener (this);
}

AudioProcessorValueTreeState::~AudioProcessorValueTreeState() {}

AudioProcessorParameter* AudioProcessorValueTreeState::createAndAddParameter (String paramID, String paramName, String labelText,
                                                                              NormalisableRange<float> r, float defaultVal,
                                                                              std::function<String (float)> valueToTextFunction,
                                                                              std::function<float (const String&)> textToValueFunction)
{
    // All parameters must be created before giving this manager a ValueTree state!
    jassert (! state.isValid());
    jassert (MessageManager::getInstance()->isThisTheMessageThread());

    Parameter* p = new Parameter (*this, paramID, paramName, labelText, r,
                                  defaultVal, valueToTextFunction, textToValueFunction);
    processor.addParameter (p);
    return p;
}

void AudioProcessorValueTreeState::addParameterListener (StringRef paramID, Listener* listener)
{
    if (Parameter* p = Parameter::getParameterForID (processor, paramID))
        p->listeners.add (listener);
}

void AudioProcessorValueTreeState::removeParameterListener (StringRef paramID, Listener* listener)
{
    if (Parameter* p = Parameter::getParameterForID (processor, paramID))
        p->listeners.remove (listener);
}

Value AudioProcessorValueTreeState::getParameterAsValue (StringRef paramID) const
{
    if (Parameter* p = Parameter::getParameterForID (processor, paramID))
        return p->state.getPropertyAsValue (valuePropertyID, undoManager);

    return Value();
}

NormalisableRange<float> AudioProcessorValueTreeState::getParameterRange (StringRef paramID) const noexcept
{
    if (Parameter* p = Parameter::getParameterForID (processor, paramID))
        return p->range;

    return NormalisableRange<float>();
}

AudioProcessorParameter* AudioProcessorValueTreeState::getParameter (StringRef paramID) const noexcept
{
    return Parameter::getParameterForID (processor, paramID);
}

float* AudioProcessorValueTreeState::getRawParameterValue (StringRef paramID) const noexcept
{
    if (Parameter* p = Parameter::getParameterForID (processor, paramID))
        return &(p->value);

    return nullptr;
}

ValueTree AudioProcessorValueTreeState::getOrCreateChildValueTree (const String& paramID)
{
    ValueTree v (state.getChildWithProperty (idPropertyID, paramID));

    if (! v.isValid())
    {
        v = ValueTree (valueType);
        v.setProperty (idPropertyID, paramID, undoManager);
        state.addChild (v, -1, undoManager);
    }

    return v;
}

void AudioProcessorValueTreeState::updateParameterConnectionsToChildTrees()
{
    if (! updatingConnections)
    {
        ScopedValueSetter<bool> svs (updatingConnections, true, false);

        const int numParams = processor.getParameters().size();

        for (int i = 0; i < numParams; ++i)
        {
            AudioProcessorParameter* const ap = processor.getParameters().getUnchecked(i);
            jassert (dynamic_cast<Parameter*> (ap) != nullptr);

            Parameter* p = static_cast<Parameter*> (ap);
            p->setNewState (getOrCreateChildValueTree (p->paramID));
        }
    }
}

void AudioProcessorValueTreeState::valueTreePropertyChanged (ValueTree&, const Identifier& property)
{
    if (property == idPropertyID)
        updateParameterConnectionsToChildTrees();
}

void AudioProcessorValueTreeState::valueTreeChildAdded (ValueTree& parent, ValueTree&)
{
    if (parent == state)
        updateParameterConnectionsToChildTrees();
}

void AudioProcessorValueTreeState::valueTreeChildRemoved (ValueTree& parent, ValueTree&, int)
{
    if (parent == state)
        updateParameterConnectionsToChildTrees();
}

void AudioProcessorValueTreeState::valueTreeRedirected (ValueTree& v)
{
    if (v == state)
        updateParameterConnectionsToChildTrees();
}

void AudioProcessorValueTreeState::valueTreeChildOrderChanged (ValueTree&, int, int) {}
void AudioProcessorValueTreeState::valueTreeParentChanged (ValueTree&) {}

void AudioProcessorValueTreeState::timerCallback()
{
    const int numParams = processor.getParameters().size();
    bool anythingUpdated = false;

    for (int i = 0; i < numParams; ++i)
    {
        AudioProcessorParameter* const ap = processor.getParameters().getUnchecked(i);
        jassert (dynamic_cast<Parameter*> (ap) != nullptr);

        Parameter* p = static_cast<Parameter*> (ap);

        if (p->needsUpdate.compareAndSetBool (0, 1))
        {
            p->copyValueToValueTree();
            anythingUpdated = true;
        }
    }

    startTimer (anythingUpdated ? 1000 / 50
                                : jlimit (50, 500, getTimerInterval() + 20));
}

AudioProcessorValueTreeState::Listener::Listener() {}
AudioProcessorValueTreeState::Listener::~Listener() {}

//==============================================================================
struct AttachedControlBase  : public AudioProcessorValueTreeState::Listener,
                              public AsyncUpdater
{
    AttachedControlBase (AudioProcessorValueTreeState& s, const String& p)
        : state (s), paramID (p), lastValue (0)
    {
        state.addParameterListener (paramID, this);
    }

    void removeListener()
    {
        state.removeParameterListener (paramID, this);
    }

    void setNewUnnormalisedValue (float newUnnormalisedValue)
    {
        if (AudioProcessorParameter* p = state.getParameter (paramID))
        {
            const float newValue = state.getParameterRange (paramID)
                                      .convertTo0to1 (newUnnormalisedValue);

            if (p->getValue() != newValue)
                p->setValueNotifyingHost (newValue);
        }
    }

    void sendInitialUpdate()
    {
        if (float* v = state.getRawParameterValue (paramID))
            parameterChanged (paramID, *v);
    }

    void parameterChanged (const String&, float newValue) override
    {
        lastValue = newValue;

        if (MessageManager::getInstance()->isThisTheMessageThread())
        {
            cancelPendingUpdate();
            setValue (newValue);
        }
        else
        {
            triggerAsyncUpdate();
        }
    }

    void handleAsyncUpdate() override
    {
        setValue (lastValue);
    }

    virtual void setValue (float) = 0;

    AudioProcessorValueTreeState& state;
    String paramID;
    float lastValue;

    JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (AttachedControlBase)
};

//==============================================================================
struct AudioProcessorValueTreeState::SliderAttachment::Pimpl  : private AttachedControlBase,
                                                                private Slider::Listener
{
    Pimpl (AudioProcessorValueTreeState& s, const String& p, Slider& sl)
        : AttachedControlBase (s, p), slider (sl)
    {
        NormalisableRange<float> range (s.getParameterRange (paramID));
        slider.setRange (range.start, range.end, range.interval);

        if (AudioProcessorParameter* param = state.getParameter (paramID))
            slider.setDoubleClickReturnValue (true, range.convertFrom0to1 (param->getDefaultValue()));

        sendInitialUpdate();
        slider.addListener (this);
    }

    ~Pimpl()
    {
        slider.removeListener (this);
        removeListener();
    }

    void setValue (float newValue) override
    {
        slider.setValue (newValue, sendNotificationSync);
    }

    void sliderValueChanged (Slider* s) override
    {
        if (! ModifierKeys::getCurrentModifiers().isRightButtonDown())
            setNewUnnormalisedValue ((float) s->getValue());
    }

    void sliderDragStarted (Slider*) override
    {
        if (AudioProcessorParameter* p = state.getParameter (paramID))
            p->beginChangeGesture();
    }

    void sliderDragEnded (Slider*) override
    {
        if (AudioProcessorParameter* p = state.getParameter (paramID))
            p->endChangeGesture();
    }

    Slider& slider;

    JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (Pimpl)
};

AudioProcessorValueTreeState::SliderAttachment::SliderAttachment (AudioProcessorValueTreeState& s, const String& p, Slider& sl)
    : pimpl (new Pimpl (s, p, sl))
{
}

AudioProcessorValueTreeState::SliderAttachment::~SliderAttachment() {}

//==============================================================================
struct AudioProcessorValueTreeState::ComboBoxAttachment::Pimpl  : private AttachedControlBase,
                                                                  private ComboBox::Listener
{
    Pimpl (AudioProcessorValueTreeState& s, const String& p, ComboBox& c)
        : AttachedControlBase (s, p), combo (c)
    {
        sendInitialUpdate();
        combo.addListener (this);
    }

    ~Pimpl()
    {
        combo.removeListener (this);
        removeListener();
    }

    void setValue (float newValue) override
    {
        combo.setSelectedItemIndex (roundToInt (newValue), sendNotificationSync);
    }

    void comboBoxChanged (ComboBox* comboBox) override
    {
        setNewUnnormalisedValue ((float) comboBox->getSelectedId() - 1.0f);
    }

    ComboBox& combo;

    JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (Pimpl)
};

AudioProcessorValueTreeState::ComboBoxAttachment::ComboBoxAttachment (AudioProcessorValueTreeState& s, const String& p, ComboBox& c)
    : pimpl (new Pimpl (s, p, c))
{
}

AudioProcessorValueTreeState::ComboBoxAttachment::~ComboBoxAttachment() {}

//==============================================================================
struct AudioProcessorValueTreeState::ButtonAttachment::Pimpl  : private AttachedControlBase,
                                                                private Button::Listener
{
    Pimpl (AudioProcessorValueTreeState& s, const String& p, Button& b)
        : AttachedControlBase (s, p), button (b)
    {
        sendInitialUpdate();
        button.addListener (this);
    }

    ~Pimpl()
    {
        button.removeListener (this);
        removeListener();
    }

    void setValue (float newValue) override
    {
        button.setToggleState (newValue >= 0.5f, sendNotificationSync);
    }

    void buttonClicked (Button* b) override
    {
        setNewUnnormalisedValue (b->getToggleState() ? 1.0f : 0.0f);
    }

    Button& button;

    JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (Pimpl)
};

AudioProcessorValueTreeState::ButtonAttachment::ButtonAttachment (AudioProcessorValueTreeState& s, const String& p, Button& b)
    : pimpl (new Pimpl (s, p, b))
{
}

AudioProcessorValueTreeState::ButtonAttachment::~ButtonAttachment() {}

#endif
